import { Page } from "puppeteer";
import { Recorder } from "./Recorder";
import { AudioRecorder } from "./AudioRecorder";
import { VideoRecorder } from "./VideoRecorder";
import { ChildProcess, spawn, exec } from "child_process";
import path from "path";
import { delay, MouseOperation } from "./utils";
import fs from "fs";
import  Live  from "./model/Live";
export const book: Record<string, DYRecorder | null | undefined | boolean> = {};

//用于录制抖音直播
export class DYRecorder extends Recorder {
    static n: number = 0;
    static max: number = 3;
    private mouseInterval: NodeJS.Timeout | null = null;
    audioRecorder: AudioRecorder;
    videoRecorder: VideoRecorder;
    private t: NodeJS.Timeout | null = null;
    public current = "";
    private isRecording = false;
    public startTime: Date | null = null;
    constructor(private page: Page, private cb?: () => void) {
        super();
        this.audioRecorder = new AudioRecorder(page);
        this.videoRecorder = new VideoRecorder(page);
        page.browser().on("disconnected", () => {
            this.stop();
        });
    }

    private async simulateMouseMovement() {
        try {
            // 读取录制的鼠标操作数据
            const operationsPath = path.join(process.cwd(), 'mouse_operations.json');
            if (!fs.existsSync(operationsPath)) {
                console.warn('未找到鼠标操作记录文件');
                return;
            }

            const operations: MouseOperation[] = JSON.parse(fs.readFileSync(operationsPath, 'utf-8'));

            // 创建CDP会话
            const client = await this.page.target().createCDPSession();

            // 确保窗口不是最小化状态
            try {
                const windowInfo = await client.send('Browser.getWindowForTarget');
                await client.send('Browser.setWindowBounds', {
                    windowId: windowInfo.windowId,
                    bounds: {
                        windowState: 'normal'
                    }
                });
                await delay(500);
            } catch (e) {
                console.warn('尝试恢复窗口状态时出错:', e);
            }

            // 随机选择一个操作
            const operation = operations[Math.floor(Math.random() * operations.length)];

            // 获取页面尺寸
            const pageSize = await this.page.evaluate(() => {
                return {
                    width: document.documentElement.clientWidth,
                    height: document.documentElement.clientHeight
                };
            });

            // 播放选中的操作
            for (let i = 0; i < operation.positions.length; i++) {
                const pos = operation.positions[i];

                // 将百分比位置转换为实际像素位置
                const x = Math.round(pos.xPercent / 100 * pageSize.width);
                const y = Math.round(pos.yPercent / 100 * pageSize.height);

                // 使用CDP Client模拟鼠标移动
                await client.send('Input.dispatchMouseEvent', {
                    type: 'mouseMoved',
                    x: x,
                    y: y
                });

                // 如果不是最后一个位置，等待到下一个时间点
                if (i < operation.positions.length - 1) {
                    const waitTime = operation.timestamps[i + 1] - operation.timestamps[i];
                    await delay(waitTime);
                }
            }
        } catch (error) {
            console.error('模拟鼠标移动时出错:', error);
        }
    }
    static async isStreaming(page: Page) {
        try {
            await delay(2000);
            return await page.$("video");
        } catch (e) {
            console.log(e);
            return false;
        }

    }
    // 新增：检查页面宽度是否正常
    private async checkPageWidth(): Promise<boolean> {
        try {
            const bodyWidth = await this.page.evaluate(() => {
                return document.body.scrollWidth;
            });
            console.log(`当前页面宽度: ${bodyWidth}px`);
            return bodyWidth < 2000;
        } catch (e) {
            console.error("检查页面宽度时出错:", e);
            return false;
        }
    }
    async getElementVisibility(selector: string): Promise<{
        isVisible: boolean;
        visiblePercentage: number;
    }> {
        try {
            return await this.page.evaluate((sel) => {
                const element = document.querySelector(sel);
                if (!element) return { isVisible: false, visiblePercentage: 0 };

                const rect = element.getBoundingClientRect();
                const windowHeight = window.innerHeight || document.documentElement.clientHeight;
                const windowWidth = window.innerWidth || document.documentElement.clientWidth;

                // 计算可见区域
                const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0);
                const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0);

                // 计算可见面积占比
                const totalArea = rect.width * rect.height;
                const visibleArea = Math.max(0, visibleWidth) * Math.max(0, visibleHeight);
                const visiblePercentage = (visibleArea / totalArea) * 100;

                return {
                    isVisible: visiblePercentage > 0,
                    visiblePercentage: Math.round(visiblePercentage)
                };
            }, selector);
        } catch (error) {
            console.error('检查元素可见性时出错:', error);
            return { isVisible: false, visiblePercentage: 0 };
        }
    }
    async ensureElementFullyVisible(selector: string, minVisibilityPercentage: number = 95): Promise<boolean> {
        try {
            // 首先检查元素当前可见度
            const initialVisibility = await this.getElementVisibility(selector);
            console.log(`元素 "${selector}" 当前可见度: ${initialVisibility.visiblePercentage}%`);

            if (initialVisibility.visiblePercentage >= minVisibilityPercentage) {
                console.log(`元素 "${selector}" 已经足够可见，无需调整`);
                return true;
            }

            // 获取元素尺寸和位置
            const elementInfo = await this.page.evaluate((sel) => {
                const element = document.querySelector(sel);
                if (!element) return null;
                const rect = element.getBoundingClientRect();
                return {
                    width: rect.width,
                    height: rect.height,
                    top: rect.top,
                    left: rect.left,
                    bottom: rect.bottom,
                    right: rect.right
                };
            }, selector);

            if (!elementInfo) {
                console.warn(`找不到元素 "${selector}"`);
                return false;
            }

            // 计算新的视口大小，确保元素完全可见
            // 添加一些额外空间以确保元素完全可见
            // 直接设置新的视口大小，确保元素完全可见
            const padding = 50;
            // 确保宽度和高度是整数，并限制最大值
            const newWidth = Math.min(Math.floor(elementInfo.right + padding), 1920); // 限制最大宽度为1920
            const newHeight = Math.min(Math.floor(elementInfo.bottom + padding), 1080);
            console.log(`调整视口大小:   -> ${newWidth}x${newHeight}`);

            // 使用CDP调整浏览器窗口大小
            const client = await this.page.target().createCDPSession();
            const windowInfo = await client.send('Browser.getWindowForTarget');

            await client.send('Browser.setWindowBounds', {
                windowId: windowInfo.windowId,
                bounds: {
                    width: newWidth,
                    height: newHeight
                }
            });

            // 等待一段时间让浏览器调整大小
            await delay(1000);

            // 再次检查元素可见度
            const finalVisibility = await this.getElementVisibility(selector);
            console.log(`调整后元素 "${selector}" 可见度: ${finalVisibility.visiblePercentage}%`);

            return finalVisibility.visiblePercentage >= minVisibilityPercentage;
        } catch (error) {
            console.error('确保元素完全可见时出错:', error);
            return false;
        }
    }

    async start(): Promise<void> {
        // 检查视频宽度
        //等待页面加载
        await delay(1000 * 10);
        let retryCount = 0;
        const maxRetries = 10;
        let isPageWidthNormal = false;

        while (retryCount < maxRetries && !isPageWidthNormal) {
            console.log(`检查页面宽度 (尝试 ${retryCount + 1}/${maxRetries})...`);
            // 首次检查页面宽度
            isPageWidthNormal = await this.checkPageWidth();

            if (isPageWidthNormal) {
                console.log("页面宽度正常，等待10秒后再次确认...");
                await delay(10000);

                // 再次检查确认页面宽度稳定
                isPageWidthNormal = await this.checkPageWidth();

                if (isPageWidthNormal) {
                    console.log("页面宽度稳定，继续录制流程");
                    break;
                } else {
                    console.log("页面宽度变化，需要刷新页面");
                }
            } else {
                console.log("页面宽度异常，需要刷新页面");
            }
            await delay(1200);
            // 如果宽度不正常，刷新页面并等待加载
            await this.page.reload().catch(console.error);
            await delay(5000); // 等待页面加载
            retryCount++;
        }
        if (!isPageWidthNormal) {
            console.warn("警告：多次尝试后页面宽度仍然异常，退出录制");
            this.cb && this.cb();
            return;
        }
        console.log("检查并调整 chatroom 元素可见度...");
        await this.ensureElementFullyVisible("#chatroom");      
        await this.page.evaluate(() => {
            let s = document.querySelectorAll('.xgplayer-controls div');
            let arr = Array.from(s);
            arr.forEach(e => {
                if (e.textContent && e.textContent.includes('标清')) {
                    window.log("切换清晰度");
                    (e as HTMLElement).click();
                }
            });
        })
        // 等待页面加载
        await delay(1000 * 10);
        this.isRecording = true;
        DYRecorder.n++;
        console.log("start recording")
        this.mouseInterval = setInterval(() => {
            this.simulateMouseMovement().catch(console.error);
        }, Math.floor(Math.random() * 1000 * 60 * 5) + 15000); // 15-35秒随机间隔

        this.audioRecorder.start();
        this.videoRecorder.start();
        this.startTime = new Date();

        const p1 = new Promise(resolve => {
            this.t = setInterval(async () => {
                if (! await DYRecorder.isStreaming(this.page)) {
                    this.t && clearInterval(this.t);
                    this.stop();
                    resolve(null);
                }
            }, 1000);
        });
        const p2 = delay(1000 * 60 * 40);
        await Promise.race([p1, p2]);
        await this.stop();

    }

    async stop(): Promise<void> {
        if (!this.isRecording) {
            return;
        }
        if (this.mouseInterval) {
            clearInterval(this.mouseInterval);
            this.mouseInterval = null;
        }
        DYRecorder.n--;
        this.isRecording = false;
     
        await Promise.all([
            this.videoRecorder.stop(),
            this.audioRecorder.stop()
        ]);

        const { startTime: audioStartTime, outputPath: audioPath } = this.audioRecorder; // audio.webm
        const { startTime: videoStartTime, outputPath: videoPath } = this.videoRecorder; // output.mp4

        // 检查路径有效性
        if (!audioPath || !videoPath) {
            console.warn('⚠️ Audio or video path is missing');
            return;
        }

        // 检查文件实际存在
        if (!fs.existsSync(audioPath) || !fs.existsSync(videoPath)) {
            console.warn('⚠️ Audio or video file does not exist on disk');
            console.warn(`Audio path: ${audioPath}`);
            console.warn(`Video path: ${videoPath}`);
            return;
        }

        const outputFinal = path.join(path.dirname(videoPath), `output_final${videoStartTime}.mp4`);
        const timeDiff = (audioStartTime - videoStartTime) / 1000; // 计算时间差（秒）
        console.log("Time difference:", timeDiff);

        // 根据操作系统选择合适的硬件加速编码器
        const isWindows = process.platform === 'win32';
        const videoEncoder = isWindows ? "h264_qsv" : "h264_videotoolbox";
        // 构建 ffmpeg 命令
        const args = timeDiff > 0
            ? ["-itsoffset", `${timeDiff}`, "-i", videoPath, "-i", audioPath,
                "-c:v", videoEncoder, "-preset", "fast", "-c:a", "aac",
                // 添加时间戳修复参数
                "-fflags", "+genpts",
                outputFinal]
            : ["-itsoffset", `${Math.abs(timeDiff)}`, "-i", audioPath, "-i", videoPath,
                "-c:v", videoEncoder, "-preset", "fast", "-c:a", "aac",
                // 添加时间戳修复参数
                "-fflags", "+genpts",
                outputFinal];
        console.log(`Merging with ffmpeg using ${isWindows ? 'QSV' : 'VideoToolbox'} acceleration:`, args.join(" "));

        // 运行 ffmpeg 进程并返回 Promise
        return new Promise<void>((resolve, reject) => {
            const ffmpeg = spawn("ffmpeg", args);

            ffmpeg.stdout.on("data", (data: any) => console.log(`stdout: ${data}`));
            ffmpeg.stderr.on("data", (data: any) => console.error(`stderr: ${data}`));
            ffmpeg.on("close", async (code: any) => {
                if (code === 0) {
                    console.log(`ffmpeg process exited with code ${code}`);
                    await this.page.browser().close();  // 关闭浏览器
                    if (fs.existsSync(audioPath)) {
                        fs.unlinkSync(audioPath);
                        console.log(`已删除原始音频文件: ${audioPath}`);
                    }

                    if (fs.existsSync(videoPath)) {
                        fs.unlinkSync(videoPath);
                        console.log(`已删除原始视频文件: ${videoPath}`);
                    }
                    this.cb && this.cb();
                    resolve();
                } else {
                    console.error(`ffmpeg process exited with code ${code}`);
                    await this.page.browser().close();  // 即使出错也关闭浏览器
                    this.cb && this.cb();
                    reject(new Error(`ffmpeg process exited with code ${code}`));
                }
            });

            ffmpeg.on("error", async (err: any) => {
                console.error('ffmpeg process encountered an error:', err);
                await this.page.browser().close();  // 发生错误时关闭浏览器
                this.cb && this.cb();
                reject(err);
            });
        });
    }
}